﻿#region MIT
/*
Engine Of Evermore (http://code.google.com/p/engineofevermore/)
Copyright (c) 2008, 2009 Michael Woerister

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EngineOfEvermore.Scene;
using Platinum;
using EngineOfEvermore.Editor.Common.Command;
using System.Diagnostics;
using System.ComponentModel;
using EngineOfEvermore.Common;
using System.Drawing;

namespace EngineOfEvermore.Editor.SceneEditor.PropertyEditor
{
    /// <summary>
    /// Creates a component binding for a component.
    /// </summary>
    /// <remarks>
    /// TODO: In the future one should be able to provide specific implementations for certain 
    /// component types by registering them with this factory.
    /// </remarks>
    static class ComponentBindingFactory
    {
        public static IComponentBinding Create( SceneObjectComponent component, 
            PropertyGrid propertyGrid, ICommandStack commandStack )
        {
            return new GenericComponentBinding( component, propertyGrid, commandStack );
        }
    }

    /// <summary>
    /// This class provides a generic component binding. It assumes that all properties are
    /// accompanied by events that indicate when the property is changed. The events be of type 
    /// System.Action or one of its generic overloads and must follow the naming convention: 
    /// [PropertyName]Changed
    /// </summary>
    class GenericComponentBinding : IComponentBinding
    {
        public SceneObjectComponent Component { get; private set; }
        public PropertyGrid PropertyGrid { get; private set; }
        public ICommandStack CommandStack { get; private set; }

        private List<PropertyBinding> _propertyBindings = new List<PropertyBinding>();

        static Dictionary<Type, Func<PropertyEditorBase>> _editorFactoryMap =
            new Dictionary<Type, Func<PropertyEditorBase>>();

        static GenericComponentBinding()
        {
            _editorFactoryMap.Add( typeof( Vector2d ), 
                () => new Editor.Common.Controls.PropertyEditors.Vector2dEditor() );

            _editorFactoryMap.Add( typeof( bool ),
                () => new Platinum.PropertyEditors.BoolEditor() );

            _editorFactoryMap.Add( typeof( Point ),
                () => new Platinum.PropertyEditors.PointEditor() );
        }

        static PropertyEditorBase _createDefaultValidatingStringEditor( Type type )
        {
            var editor = new Platinum.PropertyEditors.ValidatingStringEditor();
            editor.Validator =
                Platinum.Validators.DefaultValidator.CreateFor( type );

            return editor;
        }

        static PropertyEditorBase _createEditorFor( PropertyDescriptor p )
        {
            if ( p.PropertyType.IsEnum )
            {
                return new Platinum.PropertyEditors.EnumEditor();
            }
            else
            {
                if ( _editorFactoryMap.ContainsKey( p.PropertyType ) )
                {
                    return _editorFactoryMap[p.PropertyType].Invoke();
                }
                else
                {
                    return _createDefaultValidatingStringEditor( p.PropertyType );
                }
            }
        }

        public GenericComponentBinding( SceneObjectComponent component, PropertyGrid propertyGrid,
            ICommandStack commandStack )
        {
            Debug.Assert( component != null );
            Debug.Assert( propertyGrid != null );
            Debug.Assert( commandStack != null );

            this.Component = component;
            this.PropertyGrid = propertyGrid;
            this.CommandStack = commandStack;

            _createComponentSection();
            _bindProperties();
        }

        private void _createComponentSection()
        {
            this.PropertyGrid.Items.Add( this.Component.GetType().Name );
        }

        private void _bindProperties()
        {
            var properties = TypeDescriptor.GetProperties( this.Component )
                .Cast<PropertyDescriptor>();

            foreach ( var p in properties )
            {
                var attributes = p.Attributes.Cast<Attribute>();

                if ( attributes.Any( a => a is PerTypeAttribute ) )
                    continue;

                if ( attributes.Any( a => a is BrowsableAttribute &&
                    !( (BrowsableAttribute) a ).Browsable ) )
                    continue;

                _bindProperty( p );
            }
        }

        private void _bindProperty( PropertyDescriptor p )
        {
            if ( !_hasEventForProperty( p ) )
            {
                throw new NotSupportedException( "This kind of ComponentBinding requires a" +
                    "changed-event for every browsable property. See class documentation." );
            }

            var eventName = _getEventNameFromPropertyName( p.Name );
            IEventAdapter eventAdapter = ActionAdapterFactory.Create( this.Component, eventName );

            var bpd = new BoundPropertyDescriptor();
            bpd.PropertyDescriptor = p;
            bpd.PropertyOwner = this.Component;

            var editor = _createEditorFor( p );

            PropertyBinding propertyBinding = new PropertyBinding( eventAdapter, bpd, editor,
                this.CommandStack );

            _propertyBindings.Add( propertyBinding );
        }

        private bool _hasEventForProperty( PropertyDescriptor p )
        {
            var events = TypeDescriptor.GetEvents( p.ComponentType );
            return events.Find( _getEventNameFromPropertyName( p.Name ), false ) != null;
        }

        private static String _getEventNameFromPropertyName( String propertyName )
        {
            return propertyName + "Changed";
        }

        public virtual void Dispose()
        {
            if ( _propertyBindings != null )
            {
                _propertyBindings.ForEach( pb => pb.Dispose() );
                _propertyBindings.Clear();
                _propertyBindings = null;
            }
        }
    }
}
